describe("HTML Document Utility functions", function () {
    "use strict";

    var doc,
        setHtml = function (html) {
            doc.documentElement.innerHTML = html;
        };

    beforeEach(function () {
        doc = document.implementation.createHTMLDocument("");
    });

    describe("rewriteCssSelectorWith", function () {
        it("should rewrite CSS rules with the new selector", function () {
            setHtml(
                "<head><style>a:hover { color: blue; }</style></head><body><span></span></body>"
            );

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /a.myFakeHover \{\s*color: blue;\s*\}/
            );
        });

        it("should rewrite CSS rules in SVG element with the new selector", function () {
            setHtml("<svg><style>.myClass { color: blue; }</style></svg>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ".myClass",
                ".myFakeClass"
            );

            expect(doc.querySelector("svg").textContent).toMatch(
                /.myFakeClass \{\s*color: blue;\s*\}/
            );
        });

        it("should correctly handle complex selectors", function () {
            setHtml("<style>body:hover span { color: blue; }</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /body.myFakeHover span \{\s*color: blue;\s*\}/
            );
        });

        it("should correctly handle simple selector occurrence", function () {
            setHtml("<style>:hover { color: blue; }</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /.myFakeHover \{\s*color: blue;\s*\}/
            );
        });

        it("should not match partial selector occurrence", function () {
            setHtml("<style>.myClass { color: blue; }</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ".my",
                ".myFakeHover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /.myClass \{\s*color: blue;\s*\}/
            );
        });

        it("should correctly handle multiple selector occurrence in same rule selector", function () {
            setHtml("<style>i:hover, a:hover { color: blue; }</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /i.myFakeHover, a.myFakeHover \{\s*color: blue;\s*\}/
            );
        });

        it("should correctly handle multiple sub-selector", function () {
            setHtml("<style>i:active::after { color: blue; }</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":active",
                ".myFakeActive"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /i.myFakeActive::?after \{\s*color: blue;\s*\}/
            );
        });

        it("should correctly handle multiple selector occurrences in different rules", function () {
            setHtml(
                "<style>a:active {color: green;}i:active { color: blue; }</style>"
            );

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":active",
                ".myFakeActive"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /i.myFakeActive \{\s*color: blue;\s*\}/
            );
        });

        it("should cope with non CSSStyleRule", function () {
            setHtml(
                '<head><style>@font-face { font-family: "RaphaelIcons"; src: url("raphaelicons-webfont.woff"); }</style></head><body><span></span></body>'
            );

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );
            expect(true).toBe(true); // work around warning from jasmine that no expectation is given
        });

        it("should not touch style elements without a matching selector", function () {
            setHtml("<style>a { color: blue; }/* a comment*/</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );

            // Use the fact that comments are discarded when processing a style sheet
            expect(doc.querySelector("style").textContent).toMatch(/a comment/);
        });

        it("should match pseudo selector independent of letter case", function () {
            setHtml("<style>a:HOver { color: blue; }/* a comment*/</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /a.myFakeHover/
            );
        });

        it("should correctly replace match with preceding not() functional form", function () {
            setHtml("<style>a:not([disabled]):hover { color: blue; }</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myFakeHover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /a(.myFakeHover:not\(\[disabled\]\)|:not\(\[disabled\]\).myFakeHover)/
            );
        });

        // On Firefox this needs a work around because of https://bugzilla.mozilla.org/show_bug.cgi?id=925493
        it("should integrate with a document loaded through ajax", function (done) {
            testHelper
                .readHTMLDocumentFixture("hover.html")
                .then(function (doc) {
                    documentUtil.rewriteCssSelectorWith(
                        doc.documentElement,
                        ":hover",
                        ".myfakehover"
                    );

                    expect(doc.querySelector("style").textContent).toMatch(
                        /body.myfakehover/
                    );
                    done();
                });
        });

        // Chrome and WebKit don't seem to care about tag name selector case sensitivity for XML documents.
        // https://github.com/cburgmer/rasterizeHTML.js/issues/91
        ifNotInWebkitOrBlinkIt("should keep letter case", function () {
            setHtml("<style>foreignObject:hover { color: blue; }</style>");

            documentUtil.rewriteCssSelectorWith(
                doc.documentElement,
                ":hover",
                ".myfakehover"
            );

            expect(doc.querySelector("style").textContent).toMatch(
                /foreignObject.myfakehover/
            );
        });
    });

    describe("lowercaseCssTypeSelectors", function () {
        it("should convert matching tag name to lower case", function () {
            setHtml("<style>A { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, ["a"]);

            expect(doc.querySelector("style").textContent).toMatch(/a \{/);
        });

        it("should convert matching tag name with mixed letter case to lower case", function () {
            setHtml("<style>sPAn { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, [
                "span",
            ]);

            expect(doc.querySelector("style").textContent).toMatch(/span \{/);
        });

        it("should convert matching tag name from a list of names to lower case", function () {
            setHtml("<style>SPAN { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, [
                "a",
                "span",
            ]);

            expect(doc.querySelector("style").textContent).toMatch(/span \{/);
        });

        it("should not touch selector parts outside of the tag name", function () {
            setHtml("<style>SPAN.aClassName { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, [
                "span",
            ]);

            expect(doc.querySelector("style").textContent).toMatch(
                /span.aClassName \{/
            );
        });

        it("should not match any tag names with a matching suffix", function () {
            setHtml("<style>LI { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, ["i"]);

            expect(doc.querySelector("style").textContent).toMatch(/LI \{/);
        });

        it("should not match any IDs with a matching alphabetical part", function () {
            setHtml("<style>#A { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, ["a"]);

            expect(doc.querySelector("style").textContent).toMatch(/#A \{/);
        });

        it("should not match a substring if the selector is in the middle of the tag list", function () {
            setHtml("<style>#timeTaken { color: blue; }");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, [
                "a",
                "meta",
                "i",
            ]);

            expect(doc.querySelector("style").textContent).toMatch(
                /#timeTaken \{/
            );
        });

        it("should convert complex selectors", function () {
            setHtml("<style>LI.COMPLEX:active { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, ["li"]);

            expect(doc.querySelector("style").textContent).toMatch(
                /li.COMPLEX:active \{/
            );
        });

        // Chrome and WebKit don't seem to care about tag name selector case sensitivity for XML documents.
        // https://github.com/cburgmer/rasterizeHTML.js/issues/91
        ifNotInWebkitOrBlinkIt(
            "should only convert mentioned tag names",
            function () {
                setHtml("<style>BODY LI { color: blue; }</style>");

                documentUtil.lowercaseCssTypeSelectors(doc.documentElement, [
                    "li",
                ]);

                expect(doc.querySelector("style").textContent).toMatch(
                    /BODY li \{/
                );
            }
        );

        it("should convert multiple different matches", function () {
            setHtml("<style>BODY LI { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, [
                "body",
                "li",
            ]);

            expect(doc.querySelector("style").textContent).toMatch(
                /body li \{/
            );
        });

        it("should convert an selector enumeration", function () {
            setHtml("<style>BODY, LI { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, [
                "body",
                "li",
            ]);

            expect(doc.querySelector("style").textContent).toMatch(
                /body, li \{/
            );
        });

        it("should be minimally invasive so we don't touch selectors that might fail unless there's actual change", function () {
            setHtml("<style>esi\\:include, a { color: blue; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, ["a"]);

            // expect this not to fail (Chrome would complain about the insertion of an invalid selector:
            // "SyntaxError: Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to parse the rule 'esi:include, a { color: blue; }'.")

            expect(true).toBe(true); // work around warning from jasmine that no expectation is given
        });

        // Document https://github.com/cburgmer/rasterizeHTML.js/issues/92
        xit("should not conflate rules targeting SVGs with ones targeting only HTML", function () {
            // lowercase matches SVG and HTML, while uppercase matches HTML only (case-insensitive)
            setHtml("<style>a { color: blue; } A { color: green; }</style>");

            documentUtil.lowercaseCssTypeSelectors(doc.documentElement, ["a"]);

            expect(doc.querySelector("style").textContent).not.toMatch(
                /\}\s*a \{ color: green/
            );
        });
    });

    describe("addClassNameRecursively", function () {
        it("should attach class to selected element", function () {
            setHtml("<span>a span</span>");

            documentUtil.addClassNameRecursively(
                doc.querySelector("span"),
                ".myClass"
            );

            expect(doc.querySelector("span").className).toMatch(/myClass/);
        });

        it("should attach the fake hover class to select the parent's elements", function () {
            setHtml("<div><ol><li>a list entry</li></ol></div>");

            documentUtil.addClassNameRecursively(
                doc.querySelector("li"),
                ".myClass"
            );

            expect(doc.querySelector("ol").className).toMatch(/myClass/);
            expect(doc.querySelector("div").className).toMatch(/myClass/);
            expect(doc.querySelector("body").className).toMatch(/myClass/);
            expect(doc.querySelector("html").className).toMatch(/myClass/);
        });

        it("should not attach the fake hover class to siblings or parent's siblings", function () {
            setHtml(
                "<div><span>a span</span><div><a>a list entry</a><i>text</i></div></div>"
            );

            documentUtil.addClassNameRecursively(
                doc.querySelector("a"),
                ".myClass"
            );

            expect(doc.querySelector("i").className).toEqual("");
            expect(doc.querySelector("span").className).toEqual("");
        });
    });

    describe("findHtmlOnlyNodeNames", function () {
        it("should find html node names", function () {
            setHtml(
                "<html><body><p><span class='whatever'><br>content</span></p></body></html>"
            );

            var nodeNames = documentUtil.findHtmlOnlyNodeNames(
                doc.documentElement
            );

            expect(nodeNames).toEqual([
                "html",
                "head",
                "body",
                "p",
                "span",
                "br",
            ]);
        });

        it("should not include tags from other namespaces", function () {
            setHtml(
                "<html><body><svg xmlns='http://www.w3.org/2000/svg'><rect/></svg></body></html>"
            );

            var nodeNames = documentUtil.findHtmlOnlyNodeNames(
                doc.documentElement
            );

            expect(nodeNames).toEqual(["html", "head", "body"]);
        });

        it("should not include HTML tags if they conflict with ones from other namespaces", function () {
            setHtml(
                "<html><body>" +
                    '<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">' +
                    '<a xlink:href="target"></a>' +
                    "</svg>" +
                    '<a href="anotherTarget"></a></body></html>'
            );

            var nodeNames = documentUtil.findHtmlOnlyNodeNames(
                doc.documentElement
            );

            expect(nodeNames).toEqual(["html", "head", "body"]);
        });
    });
});
